library(Seurat)
library(SnapATAC)
Loading required package: Matrix
Loading required package: rhdf5
library(tidyverse)
── Attaching packages ───────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.2.1     ✔ purrr   0.3.3
✔ tibble  2.1.3     ✔ dplyr   0.8.3
✔ tidyr   1.0.0     ✔ stringr 1.4.0
✔ readr   1.3.1     ✔ forcats 0.4.0
── Conflicts ──────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ tidyr::expand() masks Matrix::expand()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
✖ tidyr::pack()   masks Matrix::pack()
✖ tidyr::unpack() masks Matrix::unpack()
library(DescTools)  # 4 AUC function
Registered S3 method overwritten by 'DescTools':
  method         from 
  reorder.factor gdata
library(glue)

Attaching package: ‘glue’

The following object is masked from ‘package:dplyr’:

    collapse
library(ggalluvial)  # 4 river plot
library(ggpubr)
Loading required package: magrittr

Attaching package: ‘magrittr’

The following object is masked from ‘package:purrr’:

    set_names

The following object is masked from ‘package:tidyr’:

    extract
source("~/multiOmic_benchmark/utils.R")
gg_color_hue <- function(n) {
  hues = seq(15, 375, length = n + 1)
  hcl(h = hues, l = 65, c = 100)[1:n]
}
## Make output directory
outdir <- "~/multiOmic_benchmark/report/output/20191113_labelTransferEDA_F74_v2/"
ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)
[1] FALSE
# model.cca <- readRDS("~/models/modelCCA_union_hvg_F74_SCElist_20191113.RDS")
# model.liger <- readRDS("~/models/modelLiger_union_hvg_F74_SCElist_20191113.RDS")
# model.conos <- readRDS("~/models/modelConos_union_hvg_F74_SCElist_20191113.RDS")
seu.cca <- readRDS("~/models/labelTransferCCA_union_hvg_F74_SCElist_20191119.RDS")
seu.liger <- readRDS("~/models/labelTransferLiger_union_hvg_F74_SCElist_20191119.RDS")
seu.conos <- readRDS("~/models/labelTransferConos_union_hvg_F74_SCElist_20191119.RDS")
integrate_features <- scan("~/intFeatures_union_hvg_2000_F74_SCElist_20191113.txt", what='')
Read 10603 items
int.list <- list(CCA=seu.cca, Liger=seu.liger, Conos=seu.conos)
# ## Make method color palette
# method.palette <- brewer_palette_4_values(names(int.list), "Set1")

Embeddings

Visualize label transfer on original ATAC data (embedded SnapATAC bins)

## Load original data
orig.ATAC <- readRDS("~/my_data/cellranger-atac110_count_30439_WSSS8038360_GRCh38-1_1_0.snapATAC.RDS")
sce.list <- readRDS("~/my_data/integrated_thymus/F74_SCElist_20191119.RDS")
orig.RNA <- sce.list$RNA
Loading required package: SingleCellExperiment
Loading required package: SummarizedExperiment
Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics
Loading required package: parallel

Attaching package: ‘BiocGenerics’

The following objects are masked from ‘package:parallel’:

    clusterApply, clusterApplyLB, clusterCall, clusterEvalQ, clusterExport, clusterMap, parApply,
    parCapply, parLapply, parLapplyLB, parRapply, parSapply, parSapplyLB

The following objects are masked from ‘package:dplyr’:

    combine, intersect, setdiff, union

The following object is masked from ‘package:Matrix’:

    which

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated,
    eval, evalq, Filter, Find, get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply, match,
    mget, order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank, rbind, Reduce, rownames,
    sapply, setdiff, sort, table, tapply, union, unique, unsplit, which, which.max, which.min

Loading required package: S4Vectors

Attaching package: ‘S4Vectors’

The following objects are masked from ‘package:dplyr’:

    first, rename

The following object is masked from ‘package:tidyr’:

    expand

The following object is masked from ‘package:Matrix’:

    expand

The following object is masked from ‘package:base’:

    expand.grid

Loading required package: IRanges

Attaching package: ‘IRanges’

The following objects are masked from ‘package:glue’:

    collapse, trim

The following objects are masked from ‘package:dplyr’:

    collapse, desc, slice

The following object is masked from ‘package:purrr’:

    reduce

Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor,
    see 'citation("Biobase")', and for packages 'citation("pkgname")'.

Loading required package: DelayedArray
Loading required package: matrixStats

Attaching package: ‘matrixStats’

The following objects are masked from ‘package:Biobase’:

    anyMissing, rowMedians

The following object is masked from ‘package:dplyr’:

    count

Loading required package: BiocParallel

Attaching package: ‘DelayedArray’

The following objects are masked from ‘package:matrixStats’:

    colMaxs, colMins, colRanges, rowMaxs, rowMins, rowRanges

The following object is masked from ‘package:purrr’:

    simplify

The following objects are masked from ‘package:base’:

    aperm, apply, rowsum


Attaching package: ‘SummarizedExperiment’

The following object is masked from ‘package:Seurat’:

    Assays
## Make SeuratObjects
atac.seu <- snapToSeurat(
    obj=orig.ATAC, 
    eigs.dims=1:20, 
    norm=TRUE,
    scale=TRUE
    )
Epoch: checking input parameters ... 
Non-unique features (rownames) present in the input matrix, making uniquePerforming log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Centering and scaling data matrix

  |                                                                                                             
  |                                                                                                       |   0%
  |                                                                                                             
  |===                                                                                                    |   3%
  |                                                                                                             
  |======                                                                                                 |   6%
  |                                                                                                             
  |=========                                                                                              |   9%
  |                                                                                                             
  |============                                                                                           |  12%
  |                                                                                                             
  |===============                                                                                        |  15%
  |                                                                                                             
  |==================                                                                                     |  18%
  |                                                                                                             
  |=====================                                                                                  |  21%
  |                                                                                                             
  |========================                                                                               |  24%
  |                                                                                                             
  |===========================                                                                            |  26%
  |                                                                                                             
  |==============================                                                                         |  29%
  |                                                                                                             
  |=================================                                                                      |  32%
  |                                                                                                             
  |====================================                                                                   |  35%
  |                                                                                                             
  |=======================================                                                                |  38%
  |                                                                                                             
  |==========================================                                                             |  41%
  |                                                                                                             
  |=============================================                                                          |  44%
  |                                                                                                             
  |================================================                                                       |  47%
  |                                                                                                             
  |====================================================                                                   |  50%
  |                                                                                                             
  |=======================================================                                                |  53%
  |                                                                                                             
  |==========================================================                                             |  56%
  |                                                                                                             
  |=============================================================                                          |  59%
  |                                                                                                             
  |================================================================                                       |  62%
  |                                                                                                             
  |===================================================================                                    |  65%
  |                                                                                                             
  |======================================================================                                 |  68%
  |                                                                                                             
  |=========================================================================                              |  71%
  |                                                                                                             
  |============================================================================                           |  74%
  |                                                                                                             
  |===============================================================================                        |  76%
  |                                                                                                             
  |==================================================================================                     |  79%
  |                                                                                                             
  |=====================================================================================                  |  82%
  |                                                                                                             
  |========================================================================================               |  85%
  |                                                                                                             
  |===========================================================================================            |  88%
  |                                                                                                             
  |==============================================================================================         |  91%
  |                                                                                                             
  |=================================================================================================      |  94%
  |                                                                                                             
  |====================================================================================================   |  97%
  |                                                                                                             
  |=======================================================================================================| 100%
atac.seu <- RenameCells(atac.seu, new.names = orig.ATAC@metaData$barcode)
## Add cell type predictions
getPredictedLabels <- function(seu.int, int.name, id.col="predicted.id", score.col="score"){
  pred.df <- seu.int$ATAC@meta.data[,c(id.col, score.col), drop=F] 
  rownames(pred.df) <- str_remove(rownames(pred.df), "^ATAC_")
  colnames(pred.df) <- c(str_c("predicted.id", "_", int.name), str_c("score", "_", int.name))
  pred.df
  }
pred.cca <- getPredictedLabels(seu.cca, "CCA", score.col = "prediction.score.max")
pred.liger <- getPredictedLabels(seu.liger, "Liger")
pred.conos <- getPredictedLabels(seu.conos, "Conos")
if (all(rownames(pred.conos) == rownames(pred.cca)) & all(rownames(pred.conos) == rownames(pred.liger))) {
  atac.seu <- AddMetaData(atac.seu, metadata = cbind(pred.cca, pred.liger, pred.conos))
} else {
  stop("Non corresponding cell names")
}
## make cell type palette
cell.types <- levels(seu.cca$RNA$annotation)
cell.type.pal <- setNames(sample(gg_color_hue(length(cell.types) )), cell.types)
atac.seu <- RunUMAP(atac.seu, reduction = "SnapATAC", reduction.name = "umap.snap", dims=1:20)
The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session15:59:03 UMAP embedding parameters a = 0.9922 b = 1.112
15:59:03 Read 5793 rows and found 20 numeric columns
15:59:03 Using Annoy for neighbor search, n_neighbors = 30
15:59:03 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:59:04 Writing NN index file to temp file /tmp/RtmpsI55IG/file18a7368b8bea
15:59:04 Searching Annoy index using 1 thread, search_k = 3000
15:59:06 Annoy recall = 100%
15:59:07 Commencing smooth kNN distance calibration using 1 thread
15:59:09 Initializing from normalized Laplacian + noise
15:59:09 Commencing optimization for 500 epochs, with 237344 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:59:24 Optimization finished
ggpubr::ggarrange(
  plotlist = list(
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA"  , cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("CCA"),
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Liger", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("Liger"),
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("Conos")
  ),
  common.legend = TRUE, ncol=3, nrow=1
) +
  ggsave(paste0(outdir, "umap_labels.png"), width=16, height = 8)
Using `as.character()` on a quosure is deprecated as of rlang 0.3.0.
Please use `as_label()` or `as_name()` instead.
This warning is displayed once per session.

pl <-     DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("CCA")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues
pl <-     DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("Conos")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues
orig.RNA.seu <- as.Seurat(orig.RNA)
orig.RNA.seu <- FindVariableFeatures(orig.RNA.seu)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
orig.RNA.seu <- ScaleData(orig.RNA.seu)
Centering and scaling data matrix

  |                                                                                                             
  |                                                                                                       |   0%
  |                                                                                                             
  |====================================================                                                   |  50%
  |                                                                                                             
  |=======================================================================================================| 100%
orig.RNA.seu <- RunPCA(orig.RNA.seu)
PC_ 1 
Positive:  TRBC2, TRBC1, HMGA1, HIST1H1C, HIST1H3H, ITM2A, HIST1H2BJ, SMIM24, TRAV13-2, FXYD2 
       TRBV7-2, PCGF5, HIST1H2BH, IL32, HIST1H2BN, CHAC1, RASD1, TRBV27, TRAV13-1, TRAV8-2 
       TRAV8-4, PTPN6, SELL, HIST1H2BG, TAGAP, TRDC, TRAV38-2DV8, TRAV29DV5, TRBV9, TRAV41 
Negative:  CALD1, COL5A2, COL6A1, COL6A2, SPARC, THY1, DCN, COL3A1, NFIB, SPARCL1 
       TSHZ2, CPE, PLAC9, NID1, FKBP10, PTN, FLRT2, MAP1B, EFEMP2, BGN 
       CXCL12, RBP1, LAMB1, AHNAK, COL1A1, COL5A1, FSTL1, LUM, LAMA4, MDK 
PC_ 2 
Positive:  SFRP1, NTRK2, PLAT, ISLR, NRK, SCARA5, ASPN, OSR1, OLFML3, MXRA8 
       CAPN6, PTPRD, PLP1, TMEFF2, CREB3L1, DKK3, CERCAM, MMP2, EBF2, SMOC2 
       CDO1, COL12A1, PDGFRA, LRRC17, THBS2, HTRA3, SFRP2, ANGPTL1, MAB21L1, MXRA5 
Negative:  MKI67, CDK1, NUSAP1, TOP2A, CCNA2, RRM2, UBE2C, BIRC5, KIFC1, TYMS 
       UBE2T, AURKB, CENPF, CENPM, CDCA8, TACC3, NCAPG, TPX2, ASF1B, CDKN3 
       GTSE1, CDCA3, HJURP, SPC25, MAD2L1, CDC20, PLK1, DLGAP5, NUF2, KIF22 
PC_ 3 
Positive:  CCNA2, NUF2, GTSE1, CDCA8, UBE2T, AURKB, PBK, CDK1, NCAPG, NDC80 
       KIFC1, CDCA3, HJURP, MAD2L1, SPC25, PLK1, KIF15, DEPDC1B, BIRC5, CDCA5 
       CKS1B, RRM2, DLGAP5, HMMR, CENPA, KIF22, KIF20A, CDCA2, CENPF, KIF2C 
Negative:  HLA-DRB5, HLA-DRA, TYROBP, HLA-DRB1, HLA-DPA1, HLA-DPB1, HLA-DQA1, C1QC, C1QB, RNASE1 
       HLA-DQB1, A2M, C1QA, CSF1R, STAB1, HCK, HLA-DMB, FOLR2, SAMHD1, LYZ 
       MS4A7, SPI1, CD36, MS4A6A, CYBB, TMEM176B, CD74, IGSF6, MPEG1, MS4A4A 
PC_ 4 
Positive:  GYPA, GYPB, NFE2, GYPE, ANK1, SLC4A1, RHAG, AHSP, DMTN, KLF1 
       GATA1, TMOD1, TMEM56, HBZ, HBG1, GMPR, C17orf99, SMIM5, HBQ1, TSPO2 
       ALAS2, PHOSPHO1, CR1L, TRIM58, HBM, EPB42, RHD, RHCE, SPTA1, SMIM1 
Negative:  TMSB10, TRBC2, CCL21, IL32, CXCL13, TRBC1, APLNR, CCL19, MIF, HMGB1 
       COX4I2, COL4A1, COL15A1, MADCAM1, FDCSP, CCL17, NTS, TNC, CDH5, FABP4 
       CAV1, COL4A2, CRIP2, RGS5, KLRB1, MYLK, IFITM1, IL33, PAPLN, HPN 
PC_ 5 
Positive:  APLNR, CDH5, CAV1, COL15A1, CRIP2, COL4A1, CCL21, KDR, CLDN5, MADCAM1 
       FABP4, IL33, COL4A2, CXCL13, TM4SF1, PODXL, CCL19, COX4I2, ESAM, BCAM 
       NTS, PAPLN, SPNS2, MYLK, C8orf4, ADGRF5, TM4SF18, TGM2, RP11-536O18.1, CXorf36 
Negative:  CSF1R, MS4A4A, CYBB, PLD4, MS4A6A, CD163, HCK, IGSF6, FOLR2, ADAP2 
       CD86, MS4A7, MARCH1, MRC1, F13A1, MPEG1, CD14, SPI1, HLA-DMB, GPR34 
       CD33, TGFBI, CLEC7A, TIMD4, CEBPA, SIGLEC1, CSF2RA, SLC15A3, LY86, AGR2 
orig.RNA.seu <- RunUMAP(orig.RNA.seu, dims=1:40)
16:03:30 UMAP embedding parameters a = 0.9922 b = 1.112
16:03:30 Read 8321 rows and found 40 numeric columns
16:03:30 Using Annoy for neighbor search, n_neighbors = 30
16:03:30 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
16:03:31 Writing NN index file to temp file /tmp/RtmpsI55IG/file18a76093336b
16:03:31 Searching Annoy index using 1 thread, search_k = 3000
16:03:34 Annoy recall = 100%
16:03:35 Commencing smooth kNN distance calibration using 1 thread
16:03:37 Initializing from normalized Laplacian + noise
16:03:37 Commencing optimization for 500 epochs, with 367644 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
16:03:58 Optimization finished
plotly::ggplotly(DimPlot(orig.RNA.seu, group.by="annotation"))

Prediction score

Quantifies the uncertainty of the prediction. Calculated differently for every method, but used to define which cells are “unassigned”.

orig.composition <- orig.RNA$annotation
orig.frac <- table(orig.composition)/length(orig.composition)
orig.frac.df <- data.frame(orig.frac) %>%
  dplyr::rename(predicted.id=orig.composition, frac.label=Freq) %>%
  mutate(method="original.RNA")
score_cols <- str_subset(colnames(atac.seu@meta.data), 'score_')
label_cols <- str_subset(colnames(atac.seu@meta.data), 'predicted.id_')
pred.labels.df <- imap(list(CCA=pred.cca, Liger=pred.liger, Conos=pred.conos), ~ 
      rownames_to_column(.x, "cell") %>%
      rename_all(funs(str_remove(., str_c("_",.y)))) %>%
      mutate(method=.y)
    ) %>%
  purrr::reduce(bind_rows) %>%
  mutate(score=ifelse(is.na(score), 0, score))
funs() is soft deprecated as of dplyr 0.8.0
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
This warning is displayed once per session.binding character and factor vector, coercing into character vector
predict_score_hist <- 
  pred.labels.df %>%
  ggplot(aes(score, fill=method)) +
  geom_histogram(position="identity", alpha=0.8, bins=40) +
  facet_grid(method ~.) +
  scale_fill_brewer(palette="Set1") +
  xlab("Label prediction score") +
  theme_bw(base_size = 16) +
  theme(legend.position = "top")
cutoffs <- seq(0,1,0.05)
predict_score_cumedist <-
  pred.labels.df %>%
  group_by(method) %>%
  mutate(bins=cut(score, breaks = cutoffs)) %>%
  mutate(score=as.numeric(str_remove_all(as.character(bins), ".+,|]"))) %>%
  ggplot(aes(score, color=method)) +
  stat_ecdf(size=0.8, alpha=0.7) +
  scale_color_brewer(palette = "Set1") +
  ylab("Fraction of unassigned cells") +
  xlab("Prediction score cutoff") +
  theme_bw(base_size = 16) +
  xlim(0,1) +
  coord_fixed() +
  guides(color="none") 
ggpubr::ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
          labels=c("A", "B")) +
  ggsave(paste0(outdir, "prediction_score_distribution.png"), height = 6, width = 10)
Removed 47 rows containing non-finite values (stat_ecdf).

ggpubr::ggarrange(
  plotlist = list(
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_CCA"  , coord.fixed = TRUE) + ggtitle("CCA"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Liger", coord.fixed = TRUE) + ggtitle("Liger"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Conos", coord.fixed = TRUE) + ggtitle("Conos")
  ),
  common.legend = TRUE, ncol=3, nrow=1
) +
  ggsave(paste0(outdir, "prediction_score_umaps.png"), height = 7, width=14)

Cell type composition

Compare cell type fractions (w uncertainty)

orig.rank.df <- orig.frac.df %>% 
  mutate(orig.rank=dense_rank(frac.label)) %>%
  select(orig.rank, predicted.id) %>%
  distinct() %>%
  arrange(orig.rank) %>%
  column_to_rownames("predicted.id") 
pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=mean(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  bind_rows(orig.frac.df) %>%
  mutate(orig.rank = orig.rank.df[predicted.id,]) %>%
  mutate(predicted.id=factor(predicted.id, levels=rownames(orig.rank.df)))%>%
  # select(method, predicted.id, frac.label) %>%
  # distinct() %>%
  ggplot(aes(predicted.id, frac.label, fill=mean.score, color=mean.score)) +
  geom_point(size=2) +
  geom_col(width=0.05) +
  coord_flip() +
  # geom_line(aes(group=method)) +
  facet_wrap(method~., nrow=1, ncol=4, scales="free_x") +
  scale_color_viridis_c() +
  scale_fill_viridis_c() +
  ylab("Fraction of cells") +
  theme_bw(base_size = 16) +
  ggsave(paste0(outdir, "cell_type_composition_bars.png"), width = 15, height = 7)
binding character and factor vector, coercing into character vector

Agreement with unsupervised clustering of ATAC data

Calculate which fractions of NNs in bin based graph of ATAC cells have the same annotation

k = 30
atac.seu <- FindNeighbors(atac.seu, assay = "ATAC", reduction = "SnapATAC", dims = 1:15, k.param = k)
Computing nearest neighbor graph
Computing SNN
atac.nn.list <- getNNlist(atac.seu)
knn.score.CCA <- test.knn(atac.nn.list, setNames(pred.cca$predicted.id_CCA, rownames(pred.cca)))
p-value will be approximate in the presence of ties
knn.score.conos <- test.knn(atac.nn.list, setNames(pred.conos$predicted.id_Conos, rownames(pred.conos)))
p-value will be approximate in the presence of ties
knn.score.liger <- test.knn(atac.nn.list, setNames(pred.liger$predicted.id_Liger, rownames(pred.liger)))
p-value will be approximate in the presence of ties
knn_score_df <-
  list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
  imap( ~ data.frame(KNN_score = .x$KNN_score, D=.x$D, p.val=.x$p.val, method=.y)) %>%
  # imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
  purrr::reduce(bind_rows) %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
  mutate(data="true")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
knn_score_null_df <-
  list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
  imap( ~ data.frame(KNN_score = .x$null, D=.x$D, p.val=.x$p.val, method=.y)) %>%
  # imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
  purrr::reduce(bind_rows) %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
  mutate(data="null")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
bind_rows(knn_score_df, knn_score_null_df) %>%
  ggplot(aes(KNN_score, color=method)) +
  stat_ecdf( aes(alpha=data), size=1) +
  # stat_ecdf(data=. %>% filter(data=="true"), size=1) +
  facet_grid(method~.) +
  scale_alpha_discrete( range=c(0.5,1), name="") +
  scale_color_brewer(palette = "Set1") +
  geom_text(data=. %>% distinct(method, D, p.val), 
            x=1, y=0.05, hjust=1,
            aes(label=glue("KNN score = {round(D, 3)}, p.value: {p.val}"), y=c(0.90, 0.95, 1))) +
  theme_bw(base_size = 16) +
  ylab("ECDF") + xlab("Fraction of KNNs with shared label") +
  ggsave(paste(outdir,"KNN_score_ecdf_unionHVG.png"), height = 6, width=7)
Using alpha for a discrete variable is not advised.

full_join(pred.labels.df, knn_score_df) %>%
  ggplot(aes(KNN_score, color=method)) +
  stat_ecdf() +
  facet_wrap("predicted.id") +
  scale_color_brewer(palette = "Set1") +
  coord_fixed()
Joining, by = c("cell", "method")

Accessibility of markers

Taking markers from Fig. S2 of JP’s manuscript

thymus.markers <- c("PTPRC", "CD3G", "TYROBP","CD19","HOXA9",'FXYD2',"SH3TC1","CCR9","CD8A", "CD8B","PDCD1", "CRTAM","CD40LG","CCR6","FOXP3","SOX13","ZNF683","KLRD1","TNFSF11","VPREB1","MS4A1", "CLEC9A", "CLEC10A", "LAMP3", "IL3RA", "FCGR3B", "C2","TPSB2",
                    'ITGA2B',"GYPA", "CDH5", "RGS5","CDH1", "PDGFRA","CRABP1")
# pbmc.markers <- c("CD79A", "MS4A1", "CD8A", "CD8B", "LYZ")
# thymus.markers <- list(Fb=c("PDGFRA", "COLEC11", "FBN1", "PI16"),
#                        VSMC=c("PDGFRB", 'ACTA2', "RGS5"),
#                        Endo=c("PECAM1", "CDH5","LYVE1"),
#                        TEC = c("EPCAM", "FOXN1", "CCL25", "CCL19")
#                        )
thymus.markers.df <- imap(thymus.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
  purrr::reduce(bind_rows)
marker.access.df <- atac.seu@assays$ACTIVITY@data[intersect(thymus.markers, rownames(atac.seu@assays$ACTIVITY)),] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
  full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
  # full_join(thymus.markers.df) %>%
  pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
  dplyr::mutate(method=str_remove(method,".+_")) %>%
  filter(method %in% c("CCA", "Liger", "Conos")) 
ordered_cell_types <- c("DN", "DP (Q)", "DP (P)", "SP", "NK", "ILC3", "DC", "Mac", "Ery", "Fib")
markers_pl <- 
  marker.access.df %>%
  mutate(predicted.id = case_when(str_detect(predicted.id, "CD8") ~ "CD8+T",
                                  # str_detect(predicted.id, "CD4") ~ "CD4+T",
                                  TRUE ~ predicted.id
                                  )
         ) %>%
  mutate(predicted.id=factor(predicted.id, levels = ordered_cell_types)) %>%
  group_by(method, predicted.id, gene) %>%
  dplyr::mutate(frac.cells=sum(log.counts > 0)/n()) %>%
  # filter(method=="CCA") %>%
  ungroup() %>%
  ggplot( aes( gene, predicted.id)) +
  geom_point(aes(size=frac.cells, color=frac.cells)) +
  facet_grid(method~., space="free", scales="free_x") +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_viridis_c() +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.x = element_text(angle=45)) 
markers_pl 

  
ggsave(paste0(outdir, "Thymus_markers_accessibility.png"), height = 16, width = 12)

Reproducing Fig.2H on T-cell development

t.cell.markers <- list(known.markers = c("CD34", "IGLL1", "TRGC2", "TRDC", "PTCRA", "TRBC2", "TRAC", "CD4", "CD8A", "CD8B"),
                       chemokine.receptors = c("CCR9", "CCR7"),
                       tcr.activation = c("CD5", "CD27"),
                       proliferation=c("PCNA", "CDK1", "MKI67"),
                       cyclin.D = c("CCND2", "CCND3"),
                       recombination=c("RAG1", "RAG2"),
                       apoptosis=c("HRK","BMF", "TP53INP1"),
                       stage.markers = c("ST18", "HIVEP3", "RGPD3", "SMPD3", "AQP3", "RORC", "SATB1", "TOX2")
                       ) 
t.cell.markers.df <- imap(t.cell.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
  purrr::reduce(bind_rows)
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
ordered.tcells <- c("DN", "DP (P)", "DP (Q)","SP (1)")
tcells.markers.df <- 
  atac.seu@assays$ACTIVITY@data[intersect(unlist(t.cell.markers), rownames(atac.seu@assays$ACTIVITY)),] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
  full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
  pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
  dplyr::mutate(method=str_remove(method,".+_")) %>%
  filter(method %in% c("CCA", "Liger", "Conos")) %>%
  mutate(predicted.id=ifelse(str_detect(predicted.id, "CD8+"), "CD8+T", predicted.id)) %>%
  mutate(predicted.id=ifelse(str_detect(predicted.id, "CD4+"), "CD4+T", predicted.id)) %>%
  filter(predicted.id %in% ordered.tcells) %>%
  group_by(method, predicted.id, gene) %>%
  dplyr::mutate(frac.cells=sum(log.counts > 0)/n(), mean.acc=mean(log.counts)) %>%
  ungroup() 
Joining, by = "cell"
Column `cell` joining factor and character vector, coercing into character vector
tcells.markers.df %>%
  full_join(t.cell.markers.df) %>%
  # filter(method=="CCA") %>%
  mutate(predicted.id=factor(predicted.id, levels=ordered.tcells)) %>%
  ggplot(aes( predicted.id, gene)) +
  facet_grid(cell.type.class~method, scales = "free_y", space="free") +
  geom_point(aes(size=frac.cells, color=mean.acc)) +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_gradient2(midpoint = 0.5) +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.y = element_text(angle=0)) 
Joining, by = "gene"
Column `gene` joining factor and character vector, coercing into character vector

ggsave(paste0(outdir, "tcell_markers.png"), height = 14, width = 14)

Thoughts

  • Conos scores a lot of cells with high confidence, but fails to assign cells to difficult clusters
  • CCA resembles the composition of the RNA data better, but curious that the other methods identify way more
LS0tCnRpdGxlOiAiTGFiZWwgdHJhbnNmZXIgRURBIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNuYXBBVEFDKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEZXNjVG9vbHMpICAjIDQgQVVDIGZ1bmN0aW9uCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ2FsbHV2aWFsKSAgIyA0IHJpdmVyIHBsb3QKbGlicmFyeShnZ3B1YnIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQpzb3VyY2UoIn4vbXVsdGlPbWljX2JlbmNobWFyay9LTk5fYWdyZWVtZW50LlIiKQoKCmdnX2NvbG9yX2h1ZSA8LSBmdW5jdGlvbihuKSB7CiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGggPSBuICsgMSkKICBoY2woaCA9IGh1ZXMsIGwgPSA2NSwgYyA9IDEwMClbMTpuXQp9CgojIyBNYWtlIG91dHB1dCBkaXJlY3RvcnkKb3V0ZGlyIDwtICJ+L211bHRpT21pY19iZW5jaG1hcmsvcmVwb3J0L291dHB1dC8yMDE5MTExM19sYWJlbFRyYW5zZmVyRURBX0Y3NF92Mi8iCmlmZWxzZSghZGlyLmV4aXN0cyhvdXRkaXIpLCBkaXIuY3JlYXRlKG91dGRpciksIEZBTFNFKQpgYGAKCgpgYGB7cn0KIyBtb2RlbC5jY2EgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxDQ0FfdW5pb25faHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLlJEUyIpCiMgbW9kZWwubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxMaWdlcl91bmlvbl9odmdfRjc0X1NDRWxpc3RfMjAxOTExMTMuUkRTIikKIyBtb2RlbC5jb25vcyA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbENvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQoKc2V1LmNjYSA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckxpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUuY29ub3MgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQoKCmludGVncmF0ZV9mZWF0dXJlcyA8LSBzY2FuKCJ+L2ludEZlYXR1cmVzX3VuaW9uX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLnR4dCIsIHdoYXQ9JycpCgppbnQubGlzdCA8LSBsaXN0KENDQT1zZXUuY2NhLCBMaWdlcj1zZXUubGlnZXIsIENvbm9zPXNldS5jb25vcykKCiMgIyMgTWFrZSBtZXRob2QgY29sb3IgcGFsZXR0ZQojIG1ldGhvZC5wYWxldHRlIDwtIGJyZXdlcl9wYWxldHRlXzRfdmFsdWVzKG5hbWVzKGludC5saXN0KSwgIlNldDEiKQoKYGBgCgojIyMgRW1iZWRkaW5ncwpWaXN1YWxpemUgbGFiZWwgdHJhbnNmZXIgb24gb3JpZ2luYWwgQVRBQyBkYXRhIChlbWJlZGRlZCBTbmFwQVRBQyBiaW5zKQpgYGB7cn0KIyMgTG9hZCBvcmlnaW5hbCBkYXRhCm9yaWcuQVRBQyA8LSByZWFkUkRTKCJ+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwQVRBQy5SRFMiKQpzY2UubGlzdCA8LSByZWFkUkRTKCJ+L215X2RhdGEvaW50ZWdyYXRlZF90aHltdXMvRjc0X1NDRWxpc3RfMjAxOTExMTkuUkRTIikKb3JpZy5STkEgPC0gc2NlLmxpc3QkUk5BCgojIyBNYWtlIFNldXJhdE9iamVjdHMKYXRhYy5zZXUgPC0gc25hcFRvU2V1cmF0KAogICAgb2JqPW9yaWcuQVRBQywgCiAgICBlaWdzLmRpbXM9MToyMCwgCiAgICBub3JtPVRSVUUsCiAgICBzY2FsZT1UUlVFCiAgICApCmF0YWMuc2V1IDwtIFJlbmFtZUNlbGxzKGF0YWMuc2V1LCBuZXcubmFtZXMgPSBvcmlnLkFUQUNAbWV0YURhdGEkYmFyY29kZSkKCiMjIEFkZCBjZWxsIHR5cGUgcHJlZGljdGlvbnMKZ2V0UHJlZGljdGVkTGFiZWxzIDwtIGZ1bmN0aW9uKHNldS5pbnQsIGludC5uYW1lLCBpZC5jb2w9InByZWRpY3RlZC5pZCIsIHNjb3JlLmNvbD0ic2NvcmUiKXsKICBwcmVkLmRmIDwtIHNldS5pbnQkQVRBQ0BtZXRhLmRhdGFbLGMoaWQuY29sLCBzY29yZS5jb2wpLCBkcm9wPUZdIAogIHJvd25hbWVzKHByZWQuZGYpIDwtIHN0cl9yZW1vdmUocm93bmFtZXMocHJlZC5kZiksICJeQVRBQ18iKQogIGNvbG5hbWVzKHByZWQuZGYpIDwtIGMoc3RyX2MoInByZWRpY3RlZC5pZCIsICJfIiwgaW50Lm5hbWUpLCBzdHJfYygic2NvcmUiLCAiXyIsIGludC5uYW1lKSkKICBwcmVkLmRmCiAgfQoKcHJlZC5jY2EgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jY2EsICJDQ0EiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKQpwcmVkLmxpZ2VyIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIpCnByZWQuY29ub3MgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jb25vcywgIkNvbm9zIikKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYSwgcHJlZC5saWdlciwgcHJlZC5jb25vcykpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CiMjIG1ha2UgY2VsbCB0eXBlIHBhbGV0dGUKY2VsbC50eXBlcyA8LSBsZXZlbHMoc2V1LmNjYSRSTkEkYW5ub3RhdGlvbikKY2VsbC50eXBlLnBhbCA8LSBzZXROYW1lcyhzYW1wbGUoZ2dfY29sb3JfaHVlKGxlbmd0aChjZWxsLnR5cGVzKSApKSwgY2VsbC50eXBlcykKCmF0YWMuc2V1IDwtIFJ1blVNQVAoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIHJlZHVjdGlvbi5uYW1lID0gInVtYXAuc25hcCIsIGRpbXM9MToyMCkKCmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBIiAgLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0xpZ2VyIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkNvbm9zIikKICApLAogIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBuY29sPTMsIG5yb3c9MQopICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInVtYXBfbGFiZWxzLnBuZyIpLCB3aWR0aD0xNiwgaGVpZ2h0ID0gOCkKCgpgYGAKCmBgYHtyfQpwbCA8LSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIikKcGxvdGx5OjpnZ3Bsb3RseShwbCkKYGBgCgpgYGB7cn0KcGwgPC0gICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ29ub3MiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQpwbG90bHk6OmdncGxvdGx5KHBsKQpgYGAKCmBgYHtyfQpvcmlnLlJOQS5zZXUgPC0gYXMuU2V1cmF0KG9yaWcuUk5BKQpvcmlnLlJOQS5zZXUgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMob3JpZy5STkEuc2V1KQpvcmlnLlJOQS5zZXUgPC0gU2NhbGVEYXRhKG9yaWcuUk5BLnNldSkKb3JpZy5STkEuc2V1IDwtIFJ1blBDQShvcmlnLlJOQS5zZXUpCm9yaWcuUk5BLnNldSA8LSBSdW5VTUFQKG9yaWcuUk5BLnNldSwgZGltcz0xOjQwKQoKcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KG9yaWcuUk5BLnNldSwgZ3JvdXAuYnk9ImFubm90YXRpb24iKSkKYGBgCgojIyBQcmVkaWN0aW9uIHNjb3JlClF1YW50aWZpZXMgdGhlIHVuY2VydGFpbnR5IG9mIHRoZSBwcmVkaWN0aW9uLiBDYWxjdWxhdGVkIGRpZmZlcmVudGx5IGZvciBldmVyeSBtZXRob2QsIGJ1dCB1c2VkIHRvIGRlZmluZSB3aGljaCBjZWxscyBhcmUgInVuYXNzaWduZWQiLgoKCmBgYHtyfQpvcmlnLmNvbXBvc2l0aW9uIDwtIG9yaWcuUk5BJGFubm90YXRpb24Kb3JpZy5mcmFjIDwtIHRhYmxlKG9yaWcuY29tcG9zaXRpb24pL2xlbmd0aChvcmlnLmNvbXBvc2l0aW9uKQoKb3JpZy5mcmFjLmRmIDwtIGRhdGEuZnJhbWUob3JpZy5mcmFjKSAlPiUKICBkcGx5cjo6cmVuYW1lKHByZWRpY3RlZC5pZD1vcmlnLmNvbXBvc2l0aW9uLCBmcmFjLmxhYmVsPUZyZXEpICU+JQogIG11dGF0ZShtZXRob2Q9Im9yaWdpbmFsLlJOQSIpCgpzY29yZV9jb2xzIDwtIHN0cl9zdWJzZXQoY29sbmFtZXMoYXRhYy5zZXVAbWV0YS5kYXRhKSwgJ3Njb3JlXycpCmxhYmVsX2NvbHMgPC0gc3RyX3N1YnNldChjb2xuYW1lcyhhdGFjLnNldUBtZXRhLmRhdGEpLCAncHJlZGljdGVkLmlkXycpCgpwcmVkLmxhYmVscy5kZiA8LSBpbWFwKGxpc3QoQ0NBPXByZWQuY2NhLCBMaWdlcj1wcmVkLmxpZ2VyLCBDb25vcz1wcmVkLmNvbm9zKSwgfiAKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKC54LCAiY2VsbCIpICU+JQogICAgICByZW5hbWVfYWxsKGZ1bnMoc3RyX3JlbW92ZSguLCBzdHJfYygiXyIsLnkpKSkpICU+JQogICAgICBtdXRhdGUobWV0aG9kPS55KQogICAgKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKHNjb3JlPWlmZWxzZShpcy5uYShzY29yZSksIDAsIHNjb3JlKSkKCnByZWRpY3Rfc2NvcmVfaGlzdCA8LSAKICBwcmVkLmxhYmVscy5kZiAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBmaWxsPW1ldGhvZCkpICsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjgsIGJpbnM9NDApICsKICBmYWNldF9ncmlkKG1ldGhvZCB+LikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgeGxhYigiTGFiZWwgcHJlZGljdGlvbiBzY29yZSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKY3V0b2ZmcyA8LSBzZXEoMCwxLDAuMDUpCnByZWRpY3Rfc2NvcmVfY3VtZWRpc3QgPC0KICBwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIG11dGF0ZShiaW5zPWN1dChzY29yZSwgYnJlYWtzID0gY3V0b2ZmcykpICU+JQogIG11dGF0ZShzY29yZT1hcy5udW1lcmljKHN0cl9yZW1vdmVfYWxsKGFzLmNoYXJhY3RlcihiaW5zKSwgIi4rLHxdIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKHNpemU9MC44LCBhbHBoYT0wLjcpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHlsYWIoIkZyYWN0aW9uIG9mIHVuYXNzaWduZWQgY2VsbHMiKSArCiAgeGxhYigiUHJlZGljdGlvbiBzY29yZSBjdXRvZmYiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB4bGltKDAsMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpIAoKZ2dwdWJyOjpnZ2FycmFuZ2UocHJlZGljdF9zY29yZV9oaXN0LCBwcmVkaWN0X3Njb3JlX2N1bWVkaXN0LCBjb21tb24ubGVnZW5kID0gVFJVRSwgd2lkdGhzID0gYygwLjgsIDEuMiksCiAgICAgICAgICBsYWJlbHM9YygiQSIsICJCIikpICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInByZWRpY3Rpb25fc2NvcmVfZGlzdHJpYnV0aW9uLnBuZyIpLCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEwKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9OH0KZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0NDQSIgICwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkNDQSIpLAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0xpZ2VyIiwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBGZWF0dXJlUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGZlYXR1cmUgPSAic2NvcmVfQ29ub3MiLCBjb29yZC5maXhlZCA9IFRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAicHJlZGljdGlvbl9zY29yZV91bWFwcy5wbmciKSwgaGVpZ2h0ID0gNywgd2lkdGg9MTQpCmBgYAoKCiMjIENlbGwgdHlwZSBjb21wb3NpdGlvbgoKQ29tcGFyZSBjZWxsIHR5cGUgZnJhY3Rpb25zICh3IHVuY2VydGFpbnR5KQoKYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD03fQpvcmlnLnJhbmsuZGYgPC0gb3JpZy5mcmFjLmRmICU+JSAKICBtdXRhdGUob3JpZy5yYW5rPWRlbnNlX3JhbmsoZnJhYy5sYWJlbCkpICU+JQogIHNlbGVjdChvcmlnLnJhbmssIHByZWRpY3RlZC5pZCkgJT4lCiAgZGlzdGluY3QoKSAlPiUKICBhcnJhbmdlKG9yaWcucmFuaykgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJwcmVkaWN0ZWQuaWQiKSAKCnByZWQubGFiZWxzLmRmICU+JQogIGdyb3VwX2J5KG1ldGhvZCkgJT4lCiAgZHJvcF9uYSgpICU+JQogIG11dGF0ZSh0b3QuY2VsbHM9bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JQogIHN1bW1hcmlzZSh0b3QubGFiZWwgPSBuKCksIHRvdC5jZWxscyA9IG1heCh0b3QuY2VsbHMpLCBtZWFuLnNjb3JlPW1lYW4oc2NvcmUpKSAlPiUKICBtdXRhdGUoZnJhYy5sYWJlbD10b3QubGFiZWwvdG90LmNlbGxzKSAlPiUKICBiaW5kX3Jvd3Mob3JpZy5mcmFjLmRmKSAlPiUKICBtdXRhdGUob3JpZy5yYW5rID0gb3JpZy5yYW5rLmRmW3ByZWRpY3RlZC5pZCxdKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWZhY3RvcihwcmVkaWN0ZWQuaWQsIGxldmVscz1yb3duYW1lcyhvcmlnLnJhbmsuZGYpKSklPiUKICAjIHNlbGVjdChtZXRob2QsIHByZWRpY3RlZC5pZCwgZnJhYy5sYWJlbCkgJT4lCiAgIyBkaXN0aW5jdCgpICU+JQogIGdncGxvdChhZXMocHJlZGljdGVkLmlkLCBmcmFjLmxhYmVsLCBmaWxsPW1lYW4uc2NvcmUsIGNvbG9yPW1lYW4uc2NvcmUpKSArCiAgZ2VvbV9wb2ludChzaXplPTIpICsKICBnZW9tX2NvbCh3aWR0aD0wLjA1KSArCiAgY29vcmRfZmxpcCgpICsKICAjIGdlb21fbGluZShhZXMoZ3JvdXA9bWV0aG9kKSkgKwogIGZhY2V0X3dyYXAobWV0aG9kfi4sIG5yb3c9MSwgbmNvbD00LCBzY2FsZXM9ImZyZWVfeCIpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArCiAgeWxhYigiRnJhY3Rpb24gb2YgY2VsbHMiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgImNlbGxfdHlwZV9jb21wb3NpdGlvbl9iYXJzLnBuZyIpLCB3aWR0aCA9IDE1LCBoZWlnaHQgPSA3KQpgYGAKCgo8IS0tIERvZXMgdGhlIHVuY2VydGFpbnR5IGRlcGVuZCBvbiB0aGUgc2l6ZSBvZiB0aGUgY2x1c3Rlcj8gLS0+CjwhLS0gYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD01fSAtLT4KCjwhLS0gcHJlZC5sYWJlbHMuZGYgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kKSAlPiUgLS0+CjwhLS0gICBkcm9wX25hKCkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKHRvdC5jZWxscz1uKCkpICU+JSAtLT4KPCEtLSAgIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCkgJT4lIC0tPgo8IS0tICAgc3VtbWFyaXNlKHRvdC5sYWJlbCA9IG4oKSwgdG90LmNlbGxzID0gbWF4KHRvdC5jZWxscyksIG1lYW4uc2NvcmU9bWVkaWFuKHNjb3JlKSwgc2Quc2NvcmU9bWFkKHNjb3JlKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGZyYWMubGFiZWw9dG90LmxhYmVsL3RvdC5jZWxscykgJT4lIC0tPgo8IS0tICAgIyBiaW5kX3Jvd3Mob3JpZy5mcmFjLmRmKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKGZyYWMubGFiZWwsIG1lYW4uc2NvcmUsIGNvbG9yPW1ldGhvZCkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWVhbi5zY29yZS1zZC5zY29yZSwgeW1heD1tZWFuLnNjb3JlK3NkLnNjb3JlKSwgYWxwaGE9MC42KSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArIC0tPgo8IS0tICAgIyBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiLCBzcGFuPTEuMikgKyAtLT4KPCEtLSAgIGZhY2V0X2dyaWQoLiB+IG1ldGhvZCkgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgc3RhdF9jb3IobGFiZWwueCA9IDAuMiwgbGFiZWwueT0wLjI1LCBjb2xvcj0iYmxhY2siLCBzaXplPTUpICAtLT4KCgo8IS0tIGBgYCAtLT4KCiMjIyBBZ3JlZW1lbnQgd2l0aCB1bnN1cGVydmlzZWQgY2x1c3RlcmluZyBvZiBBVEFDIGRhdGEKQ2FsY3VsYXRlIHdoaWNoIGZyYWN0aW9ucyBvZiBOTnMgaW4gYmluIGJhc2VkIGdyYXBoIG9mIEFUQUMgY2VsbHMgaGF2ZSB0aGUgc2FtZSBhbm5vdGF0aW9uCmBgYHtyfQprID0gMzAKYXRhYy5zZXUgPC0gRmluZE5laWdoYm9ycyhhdGFjLnNldSwgYXNzYXkgPSAiQVRBQyIsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIGRpbXMgPSAxOjE1LCBrLnBhcmFtID0gaykKCmF0YWMubm4ubGlzdCA8LSBnZXROTmxpc3QoYXRhYy5zZXUpCgprbm4uc2NvcmUuQ0NBIDwtIHRlc3Qua25uKGF0YWMubm4ubGlzdCwgc2V0TmFtZXMocHJlZC5jY2EkcHJlZGljdGVkLmlkX0NDQSwgcm93bmFtZXMocHJlZC5jY2EpKSkKa25uLnNjb3JlLmNvbm9zIDwtIHRlc3Qua25uKGF0YWMubm4ubGlzdCwgc2V0TmFtZXMocHJlZC5jb25vcyRwcmVkaWN0ZWQuaWRfQ29ub3MsIHJvd25hbWVzKHByZWQuY29ub3MpKSkKa25uLnNjb3JlLmxpZ2VyIDwtIHRlc3Qua25uKGF0YWMubm4ubGlzdCwgc2V0TmFtZXMocHJlZC5saWdlciRwcmVkaWN0ZWQuaWRfTGlnZXIsIHJvd25hbWVzKHByZWQubGlnZXIpKSkKCmtubl9zY29yZV9kZiA8LQogIGxpc3QoQ0NBPWtubi5zY29yZS5DQ0EsIGNvbm9zPWtubi5zY29yZS5jb25vcywgbGlnZXI9a25uLnNjb3JlLmxpZ2VyKSAlPiUKICBpbWFwKCB+IGRhdGEuZnJhbWUoS05OX3Njb3JlID0gLngkS05OX3Njb3JlLCBEPS54JEQsIHAudmFsPS54JHAudmFsLCBtZXRob2Q9LnkpKSAlPiUKICAjIGltYXAoIH4gZGF0YS5mcmFtZShLTk5fc2NvcmUgPSAueCRLTk5fc2NvcmUsIGNlbGw9IG5hbWVzKC54JEtOTl9zY29yZSksIEQ9LngkRCwgcC52YWw9LngkcC52YWwsIG1ldGhvZD0ueSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKSAlPiUKICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSkgJT4lCiAgbXV0YXRlKGRhdGE9InRydWUiKQprbm5fc2NvcmVfbnVsbF9kZiA8LQogIGxpc3QoQ0NBPWtubi5zY29yZS5DQ0EsIGNvbm9zPWtubi5zY29yZS5jb25vcywgbGlnZXI9a25uLnNjb3JlLmxpZ2VyKSAlPiUKICBpbWFwKCB+IGRhdGEuZnJhbWUoS05OX3Njb3JlID0gLngkbnVsbCwgRD0ueCRELCBwLnZhbD0ueCRwLnZhbCwgbWV0aG9kPS55KSkgJT4lCiAgIyBpbWFwKCB+IGRhdGEuZnJhbWUoS05OX3Njb3JlID0gLngkS05OX3Njb3JlLCBjZWxsPSBuYW1lcygueCRLTk5fc2NvcmUpLCBEPS54JEQsIHAudmFsPS54JHAudmFsLCBtZXRob2Q9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSkpICU+JQogIG11dGF0ZShkYXRhPSJudWxsIikKCgpiaW5kX3Jvd3Moa25uX3Njb3JlX2RmLCBrbm5fc2NvcmVfbnVsbF9kZikgJT4lCiAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPW1ldGhvZCkpICsKICBzdGF0X2VjZGYoIGFlcyhhbHBoYT1kYXRhKSwgc2l6ZT0xKSArCiAgIyBzdGF0X2VjZGYoZGF0YT0uICU+JSBmaWx0ZXIoZGF0YT09InRydWUiKSwgc2l6ZT0xKSArCiAgZmFjZXRfZ3JpZChtZXRob2R+LikgKwogIHNjYWxlX2FscGhhX2Rpc2NyZXRlKCByYW5nZT1jKDAuNSwxKSwgbmFtZT0iIikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgZ2VvbV90ZXh0KGRhdGE9LiAlPiUgZGlzdGluY3QobWV0aG9kLCBELCBwLnZhbCksIAogICAgICAgICAgICB4PTEsIHk9MC4wNSwgaGp1c3Q9MSwKICAgICAgICAgICAgYWVzKGxhYmVsPWdsdWUoIktOTiBzY29yZSA9IHtyb3VuZChELCAzKX0sIHAudmFsdWU6IHtwLnZhbH0iKSwgeT1jKDAuOTAsIDAuOTUsIDEpKSkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgeWxhYigiRUNERiIpICsgeGxhYigiRnJhY3Rpb24gb2YgS05OcyB3aXRoIHNoYXJlZCBsYWJlbCIpICsKICBnZ3NhdmUocGFzdGUob3V0ZGlyLCJLTk5fc2NvcmVfZWNkZl91bmlvbkhWRy5wbmciKSwgaGVpZ2h0ID0gNiwgd2lkdGg9NykKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KCmZ1bGxfam9pbihwcmVkLmxhYmVscy5kZiwga25uX3Njb3JlX2RmKSAlPiUKICBnZ3Bsb3QoYWVzKEtOTl9zY29yZSwgY29sb3I9bWV0aG9kKSkgKwogIHN0YXRfZWNkZigpICsKICBmYWNldF93cmFwKCJwcmVkaWN0ZWQuaWQiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBjb29yZF9maXhlZCgpCmBgYAoKPCEtLSAjIyMjIFdoaWNoIGNlbGxzIGFyZSBpbmNvbnNpc3RlbnRseSBhbGlnbmVkPyAtLT4KPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTEwfSAtLT4KPCEtLSBwcmVkLmxhYmVscy5kZiAlPiUgLS0+CjwhLS0gICBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGNlbGwpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKGlzLm5hKHByZWRpY3RlZC5pZCksICJub25lIiwgcHJlZGljdGVkLmlkKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh4PW1ldGhvZCwgc3RyYXR1bT1wcmVkaWN0ZWQuaWQsIGFsbHV2aXVtPWNlbGwsIGZpbGw9cHJlZGljdGVkLmlkLCBsYWJlbD1wcmVkaWN0ZWQuaWQpKSArIC0tPgo8IS0tICAgZ2VvbV9mbG93KCkgKyAtLT4KPCEtLSAgIGdlb21fc3RyYXR1bShjb2xvcj1OQSkgKyAtLT4KPCEtLSAgIGdlb21fdGV4dChzdGF0PSJzdHJhdHVtIikgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gIyMjIyBXaGljaCBjZWxscyBhcmUgaW5jb25zaXN0ZW50bHkgc2NvcmVkPyAtLT4KPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTh9IC0tPgo8IS0tIGxpYnJhcnkoZ2dhbGx1dmlhbCkgLS0+CjwhLS0gcHJlZC5sYWJlbHMuZGYgJT4lIC0tPgo8IS0tICAgc2VsZWN0KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBjZWxsKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUocHJlZGljdGVkLmlkPWlmZWxzZShpcy5uYShwcmVkaWN0ZWQuaWQpLCAibm9uZSIsIHByZWRpY3RlZC5pZCkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoeD1tZXRob2QsIHN0cmF0dW09cHJlZGljdGVkLmlkLCBhbGx1dml1bT1jZWxsLCBmaWxsPXByZWRpY3RlZC5pZCwgbGFiZWw9cHJlZGljdGVkLmlkKSkgKyAtLT4KPCEtLSAgIGdlb21fZmxvdygpICsgLS0+CjwhLS0gICBnZW9tX3N0cmF0dW0oKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KHN0YXQ9InN0cmF0dW0iKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpIC0tPgo8IS0tIGBgYCAtLT4KCiMjIEFjY2Vzc2liaWxpdHkgb2YgbWFya2VycwpUYWtpbmcgbWFya2VycyBmcm9tIEZpZy4gUzIgb2YgSlAncyBtYW51c2NyaXB0CmBgYHtyLCBmaWcuaGVpZ2h0PTEzLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnRoeW11cy5tYXJrZXJzIDwtIGMoIlBUUFJDIiwgIkNEM0ciLCAiVFlST0JQIiwiQ0QxOSIsIkhPWEE5IiwnRlhZRDInLCJTSDNUQzEiLCJDQ1I5IiwiQ0Q4QSIsICJDRDhCIiwiUERDRDEiLCAiQ1JUQU0iLCJDRDQwTEciLCJDQ1I2IiwiRk9YUDMiLCJTT1gxMyIsIlpORjY4MyIsIktMUkQxIiwiVE5GU0YxMSIsIlZQUkVCMSIsIk1TNEExIiwgIkNMRUM5QSIsICJDTEVDMTBBIiwgIkxBTVAzIiwgIklMM1JBIiwgIkZDR1IzQiIsICJDMiIsIlRQU0IyIiwKICAgICAgICAgICAgICAgICAgICAnSVRHQTJCJywiR1lQQSIsICJDREg1IiwgIlJHUzUiLCJDREgxIiwgIlBER0ZSQSIsIkNSQUJQMSIpCiMgcGJtYy5tYXJrZXJzIDwtIGMoIkNENzlBIiwgIk1TNEExIiwgIkNEOEEiLCAiQ0Q4QiIsICJMWVoiKQojIHRoeW11cy5tYXJrZXJzIDwtIGxpc3QoRmI9YygiUERHRlJBIiwgIkNPTEVDMTEiLCAiRkJOMSIsICJQSTE2IiksCiMgICAgICAgICAgICAgICAgICAgICAgICBWU01DPWMoIlBER0ZSQiIsICdBQ1RBMicsICJSR1M1IiksCiMgICAgICAgICAgICAgICAgICAgICAgICBFbmRvPWMoIlBFQ0FNMSIsICJDREg1IiwiTFlWRTEiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIFRFQyA9IGMoIkVQQ0FNIiwgIkZPWE4xIiwgIkNDTDI1IiwgIkNDTDE5IikKIyAgICAgICAgICAgICAgICAgICAgICAgICkKdGh5bXVzLm1hcmtlcnMuZGYgPC0gaW1hcCh0aHltdXMubWFya2VycywgfiBkYXRhLmZyYW1lKGdlbmU9LngsIGNlbGwudHlwZS5jbGFzcz0ueSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKQoKbWFya2VyLmFjY2Vzcy5kZiA8LSBhdGFjLnNldUBhc3NheXMkQUNUSVZJVFlAZGF0YVtpbnRlcnNlY3QodGh5bXVzLm1hcmtlcnMsIHJvd25hbWVzKGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWSkpLF0gJT4lCiAgYXMubWF0cml4KCkgJT4lCiAgcmVzaGFwZTI6Om1lbHQodmFybmFtZXM9YygiZ2VuZSIsICJjZWxsIiksIHZhbHVlLm5hbWU9ImxvZy5jb3VudHMiKSAlPiUKICBmdWxsX2pvaW4ocm93bmFtZXNfdG9fY29sdW1uKGF0YWMuc2V1QG1ldGEuZGF0YVssIGxhYmVsX2NvbHNdLCAiY2VsbCIpKSAlPiUKICAjIGZ1bGxfam9pbih0aHltdXMubWFya2Vycy5kZikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9bGFiZWxfY29scywgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gInByZWRpY3RlZC5pZCIpICU+JQogIGRwbHlyOjptdXRhdGUobWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCIuK18iKSkgJT4lCiAgZmlsdGVyKG1ldGhvZCAlaW4lIGMoIkNDQSIsICJMaWdlciIsICJDb25vcyIpKSAKCm9yZGVyZWRfY2VsbF90eXBlcyA8LSBjKCJETiIsICJEUCAoUSkiLCAiRFAgKFApIiwgIlNQICgxKSIsICJOSyIsICJJTEMzIiwgIkRDIiwgIk1hYyIsICJFcnkiLCAiRmliIikKCm1hcmtlcnNfcGwgPC0gCiAgbWFya2VyLmFjY2Vzcy5kZiAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkID0gY2FzZV93aGVuKHN0cl9kZXRlY3QocHJlZGljdGVkLmlkLCAiQ0Q4IikgfiAiQ0Q4K1QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzdHJfZGV0ZWN0KHByZWRpY3RlZC5pZCwgIkNENCIpIH4gIkNENCtUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBwcmVkaWN0ZWQuaWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWZhY3RvcihwcmVkaWN0ZWQuaWQsIGxldmVscyA9IG9yZGVyZWRfY2VsbF90eXBlcykpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBnZW5lKSAlPiUKICBkcGx5cjo6bXV0YXRlKGZyYWMuY2VsbHM9c3VtKGxvZy5jb3VudHMgPiAwKS9uKCkpICU+JQogICMgZmlsdGVyKG1ldGhvZD09IkNDQSIpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBnZ3Bsb3QoIGFlcyggZ2VuZSwgcHJlZGljdGVkLmlkKSkgKwogIGdlb21fcG9pbnQoYWVzKHNpemU9ZnJhYy5jZWxscywgY29sb3I9ZnJhYy5jZWxscykpICsKICBmYWNldF9ncmlkKG1ldGhvZH4uLCBzcGFjZT0iZnJlZSIsIHNjYWxlcz0iZnJlZV94IikgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGhpZ2g9ImRhcmtibHVlIiwgbG93PSJ3aGl0ZSIpICsKICAjIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xLCB2anVzdD0wLjUpLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSkpIAoKbWFya2Vyc19wbCAKICAKZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJUaHltdXNfbWFya2Vyc19hY2Nlc3NpYmlsaXR5LnBuZyIpLCBoZWlnaHQgPSAxNiwgd2lkdGggPSAxMikKYGBgCgpSZXByb2R1Y2luZyBGaWcuMkggb24gVC1jZWxsIGRldmVsb3BtZW50CmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTJ9CnQuY2VsbC5tYXJrZXJzIDwtIGxpc3Qoa25vd24ubWFya2VycyA9IGMoIkNEMzQiLCAiSUdMTDEiLCAiVFJHQzIiLCAiVFJEQyIsICJQVENSQSIsICJUUkJDMiIsICJUUkFDIiwgIkNENCIsICJDRDhBIiwgIkNEOEIiKSwKICAgICAgICAgICAgICAgICAgICAgICBjaGVtb2tpbmUucmVjZXB0b3JzID0gYygiQ0NSOSIsICJDQ1I3IiksCiAgICAgICAgICAgICAgICAgICAgICAgdGNyLmFjdGl2YXRpb24gPSBjKCJDRDUiLCAiQ0QyNyIpLAogICAgICAgICAgICAgICAgICAgICAgIHByb2xpZmVyYXRpb249YygiUENOQSIsICJDREsxIiwgIk1LSTY3IiksCiAgICAgICAgICAgICAgICAgICAgICAgY3ljbGluLkQgPSBjKCJDQ05EMiIsICJDQ05EMyIpLAogICAgICAgICAgICAgICAgICAgICAgIHJlY29tYmluYXRpb249YygiUkFHMSIsICJSQUcyIiksCiAgICAgICAgICAgICAgICAgICAgICAgYXBvcHRvc2lzPWMoIkhSSyIsIkJNRiIsICJUUDUzSU5QMSIpLAogICAgICAgICAgICAgICAgICAgICAgIHN0YWdlLm1hcmtlcnMgPSBjKCJTVDE4IiwgIkhJVkVQMyIsICJSR1BEMyIsICJTTVBEMyIsICJBUVAzIiwgIlJPUkMiLCAiU0FUQjEiLCAiVE9YMiIpCiAgICAgICAgICAgICAgICAgICAgICAgKSAKdC5jZWxsLm1hcmtlcnMuZGYgPC0gaW1hcCh0LmNlbGwubWFya2VycywgfiBkYXRhLmZyYW1lKGdlbmU9LngsIGNlbGwudHlwZS5jbGFzcz0ueSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKQoKb3JkZXJlZC50Y2VsbHMgPC0gYygiRE4iLCAiRFAgKFApIiwgIkRQIChRKSIsIlNQICgxKSIpCgp0Y2VsbHMubWFya2Vycy5kZiA8LSAKICBhdGFjLnNldUBhc3NheXMkQUNUSVZJVFlAZGF0YVtpbnRlcnNlY3QodW5saXN0KHQuY2VsbC5tYXJrZXJzKSwgcm93bmFtZXMoYXRhYy5zZXVAYXNzYXlzJEFDVElWSVRZKSksXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSwgdmFsdWUubmFtZT0ibG9nLmNvdW50cyIpICU+JQogIGZ1bGxfam9pbihyb3duYW1lc190b19jb2x1bW4oYXRhYy5zZXVAbWV0YS5kYXRhWywgbGFiZWxfY29sc10sICJjZWxsIikpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPWxhYmVsX2NvbHMsIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJwcmVkaWN0ZWQuaWQiKSAlPiUKICBkcGx5cjo6bXV0YXRlKG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwiLitfIikpICU+JQogIGZpbHRlcihtZXRob2QgJWluJSBjKCJDQ0EiLCAiTGlnZXIiLCAiQ29ub3MiKSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2Uoc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDgrIiksICJDRDgrVCIsIHByZWRpY3RlZC5pZCkpICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKHN0cl9kZXRlY3QocHJlZGljdGVkLmlkLCAiQ0Q0KyIpLCAiQ0Q0K1QiLCBwcmVkaWN0ZWQuaWQpKSAlPiUKICBmaWx0ZXIocHJlZGljdGVkLmlkICVpbiUgb3JkZXJlZC50Y2VsbHMpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBnZW5lKSAlPiUKICBkcGx5cjo6bXV0YXRlKGZyYWMuY2VsbHM9c3VtKGxvZy5jb3VudHMgPiAwKS9uKCksIG1lYW4uYWNjPW1lYW4obG9nLmNvdW50cykpICU+JQogIHVuZ3JvdXAoKSAKCnRjZWxscy5tYXJrZXJzLmRmICU+JQogIGZ1bGxfam9pbih0LmNlbGwubWFya2Vycy5kZikgJT4lCiAgIyBmaWx0ZXIobWV0aG9kPT0iQ0NBIikgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1mYWN0b3IocHJlZGljdGVkLmlkLCBsZXZlbHM9b3JkZXJlZC50Y2VsbHMpKSAlPiUKICBnZ3Bsb3QoYWVzKCBwcmVkaWN0ZWQuaWQsIGdlbmUpKSArCiAgZmFjZXRfZ3JpZChjZWxsLnR5cGUuY2xhc3N+bWV0aG9kLCBzY2FsZXMgPSAiZnJlZV95Iiwgc3BhY2U9ImZyZWUiKSArCiAgZ2VvbV9wb2ludChhZXMoc2l6ZT1mcmFjLmNlbGxzLCBjb2xvcj1tZWFuLmFjYykpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudChoaWdoPSJkYXJrYmx1ZSIsIGxvdz0id2hpdGUiKSArCiAgIyBzY2FsZV9jb2xvcl9ncmFkaWVudDIobWlkcG9pbnQgPSAwLjUpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xLCB2anVzdD0wLjUpLAogICAgICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZT0wKSkgCgpnZ3NhdmUocGFzdGUwKG91dGRpciwgInRjZWxsX21hcmtlcnMucG5nIiksIGhlaWdodCA9IDE0LCB3aWR0aCA9IDE0KQoKYGBgCgo8IS0tICMjIyBDb21wYXJlIGZlYXR1cmUgc2VsZWN0aW9uIHN0cmF0ZWd5IChyZWZlcmVuY2UgYmFzZWQpIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBzZXUuY2NhLnJlZiA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3JlZmVyZW5jZV9odmdfRjc0X1NDRWxpc3RfMjAxOTExMDEuUkRTIikgLS0+CjwhLS0gc2V1LmxpZ2VyLnJlZiA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyTGlnZXJfcmVmZXJlbmNlX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKSAtLT4KPCEtLSBzZXUuY29ub3MucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDb25vc19yZWZlcmVuY2VfaHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLlJEUyIpIC0tPgoKPCEtLSBpbnRlZ3JhdGVfZmVhdHVyZXNfcmVmIDwtIHNjYW4oIn4vbW9kZWxzL2ludEZlYXR1cmVzX3JlZmVyZW5jZV9odmdfMjAwMF9GNzRfU0NFbGlzdF8yMDE5MTEwMS50eHQiLCB3aGF0ID0gIiIpIC0tPgoKPCEtLSBpbnQubGlzdC5yZWYgPC0gbGlzdChDQ0E9c2V1LmNjYS5yZWYsIExpZ2VyPXNldS5saWdlci5yZWYsIENvbm9zPXNldS5jb25vcy5yZWYpIC0tPgoKPCEtLSAjIyBBZGQgdG8gYXRhYyBTZXVyYXQgb2JqZWN0IC0tPgo8IS0tIHByZWQuY2NhLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNjYS5yZWYsICJDQ0FfcmVmIiwgc2NvcmUuY29sID0gInByZWRpY3Rpb24uc2NvcmUubWF4IikgLS0+CjwhLS0gcHJlZC5saWdlci5yZWYgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5saWdlci5yZWYsICJMaWdlcl9yZWYiKSAtLT4KPCEtLSBwcmVkLmNvbm9zLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNvbm9zLnJlZiwgIkNvbm9zX3JlZiIpIC0tPgoKPCEtLSBpZiAoYWxsKHJvd25hbWVzKHByZWQuY29ub3MpID09IHJvd25hbWVzKHByZWQuY2NhKSkgJiBhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5saWdlcikpKSB7IC0tPgo8IS0tICAgYXRhYy5zZXUgPC0gQWRkTWV0YURhdGEoYXRhYy5zZXUsIG1ldGFkYXRhID0gY2JpbmQocHJlZC5jY2EucmVmLCBwcmVkLmxpZ2VyLnJlZiwgcHJlZC5jb25vcy5yZWYpKSAtLT4KPCEtLSB9IGVsc2UgeyAtLT4KPCEtLSAgIHN0b3AoIk5vbiBjb3JyZXNwb25kaW5nIGNlbGwgbmFtZXMiKSAtLT4KPCEtLSB9IC0tPgoKPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTksIGZpZy5oZWlnaHQ9OX0gLS0+CjwhLS0gZ2dwdWJyOjpnZ2FycmFuZ2UoIC0tPgo8IS0tICAgcGxvdGxpc3QgPSBsaXN0KCAtLT4KPCEtLSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0FfcmVmIiAgLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIiksIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0xpZ2VyX3JlZiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJMaWdlciIpLCAtLT4KPCEtLSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9Db25vc19yZWYiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKSAtLT4KPCEtLSAgICksIC0tPgo8IS0tICAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xIC0tPgo8IS0tICkgIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNn0gLS0+CjwhLS0gcHJlZC5sYWJlbHMucmVmLmRmIDwtIGltYXAobGlzdChDQ0E9cHJlZC5jY2EucmVmLCBMaWdlcj1wcmVkLmxpZ2VyLnJlZiwgQ29ub3M9cHJlZC5jb25vcy5yZWYpLCB+ICAtLT4KPCEtLSAgICAgICByb3duYW1lc190b19jb2x1bW4oLngsICJjZWxsIikgJT4lIC0tPgo8IS0tICAgICAgIHJlbmFtZV9hbGwoZnVucyhzdHJfcmVtb3ZlKC4sIHN0cl9jKCJfIiwueSkpKSkgJT4lIC0tPgo8IS0tICAgICAgIG11dGF0ZShtZXRob2Q9LnkpIC0tPgo8IS0tICAgICApICU+JSAtLT4KPCEtLSAgIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoc2NvcmU9aWZlbHNlKGlzLm5hKHNjb3JlX3JlZiksIDAsIHNjb3JlX3JlZikpIC0tPgoKPCEtLSBmdWxsX2pvaW4oIC0tPgo8IS0tICAgcHJlZC5sYWJlbHMuZGYsIC0tPgo8IS0tICAgc2VsZWN0KHByZWQubGFiZWxzLnJlZi5kZiwgY2VsbCwgcHJlZGljdGVkLmlkX3JlZiwgc2NvcmVfcmVmLCBtZXRob2QpLCAtLT4KPCEtLSAgIGJ5PWMoImNlbGwiLCAibWV0aG9kIikgLS0+CjwhLS0gICApICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUobl9wcmVkPW4oKSkgJT4lIC0tPgo8IS0tICAgdW5ncm91cCgpICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBwcmVkaWN0ZWQuaWRfcmVmKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2Uobj1uKCksIG5fcHJlZD1tYXgobl9wcmVkKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGZyYWM9bi9uX3ByZWQpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMocHJlZGljdGVkLmlkLCBwcmVkaWN0ZWQuaWRfcmVmKSkgKyAtLT4KPCEtLSAgIGdlb21fdGlsZShhZXMoZmlsbD1mcmFjKSkgKyAtLT4KPCEtLSAgIGZhY2V0X3dyYXAobWV0aG9kfi4sIG5yb3c9MSwgbmNvbD0zKSArIC0tPgo8IS0tICAgY29vcmRfZml4ZWQoKSArIC0tPgo8IS0tICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IndoaXRlIiwgaGlnaD0icmVkIikgKyAtLT4KPCEtLSAgIHlsYWIoIkZlYXQuIHNlbGVjdGlvbjogcmVmZXJlbmNlIEhWRyIpICsgeGxhYigiRmVhdC4gc2VsZWN0aW9uOiB1bmlvbiBIVkciKSArIC0tPgo8IS0tICAgdGhlbWVfY293cGxvdChmb250X3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkgKyAtLT4KPCEtLSAgIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW5pb25WU3JlZmVyZW5jZS5wbmciKSwgaGVpZ2h0ID0gMTIsIHdpZHRoPTEwKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgoKPCEtLSBzY29yZS5DQ0EucmVmIDwtICAgaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYS5yZWZbLngsMV0gPT0gcHJlZC5jY2EucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuQ29ub3MucmVmIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vcy5yZWZbLngsMV0gPT0gcHJlZC5jb25vcy5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5MaWdlci5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyLnJlZlsueCwxXSA9PSBwcmVkLmxpZ2VyLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgoKPCEtLSBrbm5fc2NvcmVfcmVmX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcy5yZWYsIHNjb3JlLkxpZ2VyLnJlZiwgc2NvcmUuQ0NBLnJlZikpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+Cgo8IS0tIHF1YW50cyA9IHNlcSgwLDEsIGJ5ID0gMC4wNSkgLS0+CjwhLS0gQVVFQ0RGX2tubl9zY29yZSA8LSBrbm5fc2NvcmVfcmVmX2RmICU+JSAtLT4KPCEtLSAgIHNwbGl0KC4kbWV0aG9kKSAlPiUgLS0+CjwhLS0gICBtYXBfZGJsKCB+IC54ICU+JSAtLT4KPCEtLSAgICAgICBhcnJhbmdlKEtOTl9zY29yZSkgJT4lICAtLT4KPCEtLSAgICAgICB7ZWNkZiguJEtOTl9zY29yZSkocXVhbnRzKX0gJT4lIEFVQyhxdWFudHMsLikgLS0+CjwhLS0gICAgICkgLS0+Cgo8IS0tIGtubl9zY29yZV9yZWZfZGYgJT4lIC0tPgo8IS0tICAgbXV0YXRlKEFVQz1BVUVDREZfa25uX3Njb3JlW21ldGhvZF0pICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1tZXRob2QsIGZpbGw9bWV0aG9kKSkgKyAtLT4KPCEtLSAgIHN0YXRfZWNkZihzaXplPTEpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAtLT4KPCEtLSAgIGdlb21fdGV4dChkYXRhPS4gJT4lIGdyb3VwX2J5KG1ldGhvZCkgJT4lIHN1bW1hcmlzZShBVUM9bWF4KEFVQykpLCAgLS0+CjwhLS0gICAgICAgICAgICAgeD0wLjA1LCBoanVzdD0wLCAtLT4KPCEtLSAgICAgICAgICAgICBhZXMobGFiZWw9Z2x1ZSgiQVVDID0ge3JvdW5kKEFVQywgMyl9IiksIHk9YygwLjkwLCAwLjk1LCAxKSkpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHlsYWIoIkVDREYiKSAgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSAjIyMgSXMgdGhlIHVuaW9uIG9yIHRoZSByZWZlcmVuY2UgYmVzdCBtYWludGFpbmluZyB0aGUgc3RydWN0dXJlIG9mIHRoZSBBVEFDPyAtLT4KPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9N30gLS0+CjwhLS0gayA9IDUwIC0tPgo8IS0tIGF0YWMuc2V1IDwtIEZpbmROZWlnaGJvcnMoYXRhYy5zZXUsIGFzc2F5ID0gIkFUQUMiLCByZWR1Y3Rpb24gPSAiU25hcEFUQUMiLCBkaW1zID0gMToxNSwgay5wYXJhbSA9IGspIC0tPgoKPCEtLSBhdGFjLm5uLmxpc3QgPC0gZ2V0Tk5saXN0KGF0YWMuc2V1KSAtLT4KCjwhLS0gc2NvcmUuQ0NBIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jY2FbLngsMV0gPT0gcHJlZC5jY2FbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5Db25vcyA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY29ub3NbLngsMV0gPT0gcHJlZC5jb25vc1sueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkxpZ2VyIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5saWdlclsueCwxXSA9PSBwcmVkLmxpZ2VyWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+Cgo8IS0tIGtubl9zY29yZV9kZiA8LSAtLT4KPCEtLSAgIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2NvcmUuQ29ub3MsIHNjb3JlLkxpZ2VyLCBzY29yZS5DQ0EpKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUgLS0+CjwhLS0gICBwaXZvdF9sb25nZXIoY29scz1zdHJfc3Vic2V0KGNvbG5hbWVzKC4pLCAic2NvcmUiKSwgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gIktOTl9zY29yZSIpICU+JSAtLT4KPCEtLSAgIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgbWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCAic2NvcmUuIikpIC0tPgoKCjwhLS0gc2NvcmUuQ0NBLnJlZiA8LSAgIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jY2EucmVmWy54LDFdID09IHByZWQuY2NhLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkNvbm9zLnJlZiA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY29ub3MucmVmWy54LDFdID09IHByZWQuY29ub3MucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuTGlnZXIucmVmIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5saWdlci5yZWZbLngsMV0gPT0gcHJlZC5saWdlci5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KCjwhLS0ga25uX3Njb3JlX3JlZl9kZiA8LSAtLT4KPCEtLSAgIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2NvcmUuQ29ub3MucmVmLCBzY29yZS5MaWdlci5yZWYsIHNjb3JlLkNDQS5yZWYpKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUgLS0+CjwhLS0gICBwaXZvdF9sb25nZXIoY29scz1zdHJfc3Vic2V0KGNvbG5hbWVzKC4pLCAic2NvcmUiKSwgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gIktOTl9zY29yZSIpICU+JSAtLT4KPCEtLSAgIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgbWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCAic2NvcmUuIikpIC0tPgoKCjwhLS0gYmluZF9yb3dzKGtubl9zY29yZV9kZiwga25uX3Njb3JlX3JlZl9kZikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGZlYXR1cmUuc2VsZWN0aW9uPWlmZWxzZShzdHJfZGV0ZWN0KG1ldGhvZCwgInJlZiIpLCAicmVmIiwgInVuaW9uIikpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICIucmVmIikpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1mZWF0dXJlLnNlbGVjdGlvbiwgZmlsbD1tZXRob2QpKSArIC0tPgo8IS0tICAgc3RhdF9lY2RmKHNpemU9MSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIC0tPgo8IS0tICAgZmFjZXRfd3JhcChtZXRob2R+LikgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgeWxhYigiRUNERiIpICsgLS0+CjwhLS0gICBnZ3RpdGxlKHBhc3RlKCJLID0iLCBrKSkgKyAtLT4KPCEtLSAgIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW5pb25WU3JlZmVyZW5jZV9LTk4ucG5nIiksIGhlaWdodCA9IDQsIHdpZHRoID0gMTApIC0tPgoKPCEtLSBgYGAgLS0+Cgo8IS0tIC0tLSAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3RseTo6Z2dwbG90bHkoRGltUGxvdChvcmlnLlJOQS5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBIikpIC0tPgo8IS0tIHBsb3RseTo6Z2dwbG90bHkoRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiKSkgLS0+CjwhLS0gYGBgIC0tPgoKCiMjIyBUaG91Z2h0cwotIENvbm9zIHNjb3JlcyBhIGxvdCBvZiBjZWxscyB3aXRoIGhpZ2ggY29uZmlkZW5jZSwgYnV0IGZhaWxzIHRvIGFzc2lnbiBjZWxscyB0byBkaWZmaWN1bHQgY2x1c3RlcnMgCi0gQ0NBIHJlc2VtYmxlcyB0aGUgY29tcG9zaXRpb24gb2YgdGhlIFJOQSBkYXRhIGJldHRlciwgYnV0IGN1cmlvdXMgdGhhdCB0aGUgb3RoZXIgbWV0aG9kcyBpZGVudGlmeSB3YXkgbW9yZSAKCgoKCgoKCgoKCg==